#!/bin/bash
##########################################################################################
# Utility for backingup folders with tar and have the log emailed
#
# Copyright © 2012 Balta Costel All rights reserved.
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice, this
#   list of conditions and the following disclaimer in the documentation and/or
#   other materials provided with the distribution.
# * Neither the name of Balta Costel nor the names of its contributors may be used to
#   endorse or promote products derived from this software without specific prior
#   written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
##########################################################################################
# Dependencies 	
# sendEmail for sending logs via email
# http://caspian.dotconf.net/menu/Software/SendEmail/sendEmail-v1.56.tar.gz
# perl Net::SSLeay for ssl sendEmail
# perl IO::Socket::SSL for ssl sendEmail
# cifs-utils for cifs mount
# curlftpfs for ftp mount
# RPMforge repo for curlftpfs
##########################################################################################

##########################################################################################
######                     START CONFIGURATION SECTION                              ######
##########################################################################################
. /etc/profile
. /etc/profile
#Define some variables
#
# Global vars
ARCH=`uname -i`
DATE=`date +%d-%m-%Y`
NOW=`date +%H:%M:%S`
HOSTNAME=`hostname -f`
LOG_FILE="/var/log/backup.log.$DATE"
FILE_NAME="daily-backup.$(date +%Y-%m-%d).tar.gz"

# generic path vars
MOUNT_TYPE="cifs"                      # target mount type: cifs, nfs, ftp  (local, USB not yet implemented)
SRC_DIR="/home /etc /var"              # what to backup
DST_DIR="backup_dir"                   # where to backup
MOUNT_POINT="/backup"                  # backup media mount point
BACKUP_TARGET="192.168.X.X"            # NAS IP

# cifs only vars
CIFS_SHARE="ShareName"                 # cifs share name
CIFS_USER="username"                   # username for cifs share 
CIFS_PASS="password"                   # password for cifs share
CIFS_DOM="DOMAIN or WORKGROUP"         # user domain 

# NFS only vars
NFS_EXPORT="/path/of/export"           # NFS Exported filesystem 
NFS_OPTS="rw,hard,intr"

# FTP only vars
FTP_USER="username" 
FTP_PASS="password"

# Email Configuration Section
EMAIL_FROM="backup_user@domain.com"
EMAIL_TO="admin@domain.com"
EMAIL_SUBJ="Backup log from $HOSTNAME"
SMTP_SRV="mail.domain.com:smtp_port"
SMTP_USER="smtp_username"
SMTP_PASS="smtp_password"

##########################################################################################
######                END CONFIGURATION SECTION                                     ######
##########################################################################################

#FUNCTION to convert byte output size to human readable format
#------------------------------ START FUNCTION ------------------------------------------#
to_human_readable() {
awk -v sum="$1" ' BEGIN {
hum[1024^3]="Gb";
hum[1024^2]="Mb";
hum[1024]="Kb";
for (x=1024^3; x>=1024; x/=1024) { 
if (sum>=x) { printf "%.2f %s\n",sum/x,hum[x]; break; } } 
if (sum<1024) print "1kb"; } '
}
#----------------------- END FUNCTION ----------------------------#

# start logging
echo "====================== RUN START on ${DATE} at `date +%H:%M:%S` =============================" >> ${LOG_FILE}

# FUNCTION that check and install dependencies as needed
#------------------------------ START FUNCTION ------------------------------------------#
function check_deps() {
if ! builtin type -p wget &>/dev/null; then
	echo " wget is not installed. installing..." >> ${LOG_FILE}
	yum -y -q install wget
fi
# verify install
if ! builtin type -p wget &>/dev/null; then
	echo " Something went wrong. try installing wget manually " >> ${LOG_FILE}
	return 1
	exit 1
fi

if ! builtin type -p perl &>/dev/null; then
	echo " perl is not installed. installing..." >> ${LOG_FILE}	
	yum -y -q install perl 
fi
# verify install
if ! builtin type -p perl &>/dev/null; then
	echo " Something went wrong. try installing perl manually " >> ${LOG_FILE}
	return 1
	exit 1
fi

if ! perl -MNet::SSLeay -e '' &>/dev/null; then
	echo " perl-Net-SSLeay is not installed. installing..." >> ${LOG_FILE}	
	yum -y -q install perl-Net-SSLeay
fi
# verify install
if ! perl -MNet::SSLeay -e '' &>/dev/null; then
	echo " Something went wrong. try installing perl-Net-SSLeay manually " >> ${LOG_FILE}
	return 1
	exit 1
fi

if ! perl -MIO::Socket::SSL -e '' &>/dev/null; then
	echo " perl-IO-Socket-SSL is not installed. installing..." >> ${LOG_FILE}	
	yum -y -q install perl-IO-Socket-SSL
fi
# verify install
if ! perl -MIO::Socket::SSL -e '' &>/dev/null; then
	echo " Something went wrong. try installing perl-IO-Socket-SSL manually " >> ${LOG_FILE}
	return 1
	exit 1
fi

if ! builtin type -p sendEmail &>/dev/null; then
	echo " sendEmail is not installed. installing..." >> ${LOG_FILE}	
	wget -q -O /tmp/sendEmail-v1.56.tar.gz http://caspian.dotconf.net/menu/Software/SendEmail/sendEmail-v1.56.tar.gz
	tar -zxf /tmp/sendEmail-v1.56.tar.gz -C /tmp/
	cp /tmp/sendEmail-v1.56/sendEmail /usr/bin/
	cp /tmp/sendEmail-v1.56/sendEmail.pl /usr/bin/
fi
# verify install
if ! builtin type -p sendEmail &>/dev/null; then
	echo " Something went wrong. try installing sendEmail manually " >> ${LOG_FILE}
	return 1
	exit 1
fi

case ${MOUNT_TYPE} in
    'cifs')
if ! builtin type -p mount.cifs &>/dev/null; then
	echo " cifs-utils is not installed. installing..." >> ${LOG_FILE}
	yum -y -q install cifs-utils
fi
# verify install
if ! builtin type -p mount.cifs &>/dev/null; then
	echo " Something went wrong. try installing cifs-utils manually " >> ${LOG_FILE}
	return 1
	exit 1
fi
    ;;
	'ftp')
# install RPMforge repo
if [ ! -f "/etc/yum.repos.d/rpmforge.repo" ]; then
	echo " RPMforge repo not installed...installing " >> ${LOG_FILE}
	rpm -Uvh --quiet http://packages.sw.be/rpmforge-release/rpmforge-release-0.5.2-2.el6.rf.${ARCH}.rpm >/dev/null
fi
# verify install of RPMforge repo
if [ ! -f "/etc/yum.repos.d/rpmforge.repo" ]; then
	echo "can't install RPMforge repo ....please do so manually"
	return 1
	exit 1
fi
# install fuse-curlftpfs
if ! builtin type -p curlftpfs &>/dev/null; then
	echo " curlftpfs is not installed. installing..." >> ${LOG_FILE}
	yum -y -q install curlftpfs
fi
# verify install of fuse-curlftpfs
if ! builtin type -p curlftpfs &>/dev/null; then
	echo " Something went wrong. try installing curlftpfs manually " >> ${LOG_FILE}
	return 1
	exit 1
fi
return 0
	;;
  esac			
}
#------------------------------ END FUNCTION --------------------------------------------#

# FUNCTION Check if mount point exists
#------------------------------ START FUNCTION ------------------------------------------#
function chk_mntpoint() {
	if [ ! -d ${MOUNT_POINT} ]; then
		echo "'${MOUNT_POINT}' does not exist ...creating..." >> ${LOG_FILE}
		mkdir ${MOUNT_POINT}
		echo "created dir'${MOUNT_POINT}' for mount point" >> ${LOG_FILE}
	else
		echo "Mount point '${MOUNT_POINT}' OK..." >> ${LOG_FILE}
	fi
return 0
}
#------------------------------ END FUNCTION --------------------------------------------#

# FUNCTION mount cifs 
#------------------------------ START FUNCTION ------------------------------------------#
function mount_cifs(){
	echo "selected cifs type target" >> ${LOG_FILE}
	#check if already mounted, if not mount
	if ! `mountpoint -q ${MOUNT_POINT}` ; then 
	echo "target not mounted...mounting..." >> ${LOG_FILE}
	mount.cifs //${BACKUP_TARGET}/${CIFS_SHARE} /${MOUNT_POINT} -o user=${CIFS_USER},dom=${CIFS_DOM},pass=${CIFS_PASS} 1>> ${LOG_FILE} 2>&1
       	if [ $? -gt 0 ]; then
        echo "ERROR: Mount failed. Aborting backup." >> ${LOG_FILE}
        return 1
        exit 0
		else
			echo "target mounting OK..." >> ${LOG_FILE}
    	fi
	else
		echo "target already mounted" >> ${LOG_FILE}
		return 0
	fi
}
#------------------------------ END FUNCTION --------------------------------------------#

# FUNCTION mount nfs
#------------------------------ START FUNCTION ------------------------------------------#
function mount_nfs(){
	echo "selected nfs type target" >> ${LOG_FILE}
	#check if already mounted, if not mount
	if ! `mountpoint -q ${MOUNT_POINT}` ; then 
	echo "target not mounted...mounting..." >> ${LOG_FILE}
		mount -t nfs -o ${NFS_OPTS} ${BACKUP_TARGET}:${NFS_EXPORT} /${MOUNT_POINT} 2>&1
    	if [ $? -gt 0 ]; then
        echo "ERROR: Mount failed. Aborting backup." >> ${LOG_FILE}
        return 1
		exit 0
		else
			echo "target mounting OK..." >> ${LOG_FILE}
			return 0
    	fi
	else
		echo "target already mounted" >> ${LOG_FILE}
	fi
}
#------------------------------ END FUNCTION --------------------------------------------#

# FUNCTION that mounts FTPFS
#------------------------------ START FUNCTION ------------------------------------------#
function mount_ftpfs() {
	#check if already mounted, if not mount
	if ! `mountpoint -q ${MOUNT_POINT}` ; then 
	echo "target not mounted...mounting..." >> ${LOG_FILE}
		curlftpfs -o user=$FTP_USER:$FTP_PASS $BACKUP_TARGET $MOUNT_POINT
    	if [ $? -gt 0 ]; then
        echo "ERROR: Mount failed. Aborting backup." >> ${LOG_FILE}
        return 1
		exit 0
		else
			echo "target mounting OK..." >> ${LOG_FILE}
			return 0
    	fi
	else
		echo "target already mounted" >> ${LOG_FILE}
	fi
}
#------------------------------ END FUNCTION --------------------------------------------#

# FUNCTION to mount backup destination target 
#------------------------------ START FUNCTION ------------------------------------------#
function mount_target() {
  case ${MOUNT_TYPE} in
    'cifs')
mount_cifs
	;;
    'nfs')
mount_nfs
	;;
    'ftp')
mount_ftpfs
echo "you have selected ftp as target. !!!free space is not reported correctly!!! " >> ${LOG_FILE}
	;;
  esac
}
#------------------------------ END FUNCTION --------------------------------------------#

# FUNCTION to unmount destination target 
#------------------------------ START FUNCTION ------------------------------------------#
function umount_target() {
	umount ${MOUNT_POINT}
	if [ $? -gt 0 ]; then
        echo "ERROR: umount failed. NOT Aborting backup. you have to check server logs" >> ${LOG_FILE}
		else
			echo "target unmounting OK..." >> ${LOG_FILE}
    	fi
}
#------------------------------ END FUNCTION --------------------------------------------#

# FUNCTION to check if we have enough free space availabe
#------------------------------ START FUNCTION ------------------------------------------#
function chk_free_space() {
# estimate archive size
ARCH_SIZE=`cd / && tar -czf - $SRC_DIR 2> >(grep -v 'socket ignored\|Removing leading' >&2) | wc -c`
#convert size to human readable format
HUM_ARCH_SIZE=`to_human_readable $ARCH_SIZE`
#Get free space
TARGET_FREE_SPACE=$((`df -P ${MOUNT_POINT} | tail -1 | awk '{print $4}' | cut -d "%" -f1` * 1024))
HUM_TARGET_FREE_SPACE=`to_human_readable $TARGET_FREE_SPACE`
if [ $ARCH_SIZE -gt $TARGET_FREE_SPACE ]
then
	echo "you don't have enough free space on destination storage. Aborting backup." >> ${LOG_FILE}
	return 1
	exit 0
else
	echo "you have $HUM_TARGET_FREE_SPACE free on destination storage. we will use $HUM_ARCH_SIZE of it" >> ${LOG_FILE}
fi
return 0
}
#------------------------------ END FUNCTION --------------------------------------------#

# FUNCTION check dst dir
#------------------------------ START FUNCTION ------------------------------------------#
function check_dst_dir(){
if [ ! -d "$MOUNT_POINT/$DST_DIR" ]; then
echo "######### Destination dir does not exist...creating....#########" >> ${LOG_FILE}
	mkdir $MOUNT_POINT/$DST_DIR
fi
return 0
}
#------------------------------ END FUNCTION --------------------------------------------#

# FUNCTION create and verify backup archive
#------------------------------ START FUNCTION ------------------------------------------#
function create_arch(){
echo "" >> ${LOG_FILE}
echo "######### START creating archive at `date +%H:%M:%S` on ${DATE}. ##########" >> ${LOG_FILE}
`tar -czf $MOUNT_POINT/$DST_DIR/$FILE_NAME $SRC_DIR 2> >(grep -v 'socket ignored\|Removing leading' >&2)`
echo "######### END creating archive at `date +%H:%M:%S` on ${DATE}. ############" >> ${LOG_FILE}
echo "" >> ${LOG_FILE}
echo "######### START verifying archive at `date +%H:%M:%S` on ${DATE}. #########" >> ${LOG_FILE}
	tar -tzf $MOUNT_POINT/$DST_DIR/$FILE_NAME 1>> ${LOG_FILE} 2>&1 >/dev/null
       	if [ $? -gt 0 ]; then
        	echo "ERROR: tar not ok." >> ${LOG_FILE}
        return 1
		exit 0
		else
echo "######### END verifying archive at `date +%H:%M:%S` on ${DATE}. ###########" >> ${LOG_FILE}
echo "" >> ${LOG_FILE}
		return 0
    	fi
}
#------------------------------ END FUNCTION --------------------------------------------#

# FUNCTION Send email
#------------------------------ START FUNCTION ------------------------------------------#
function send_email(){
echo "######### Emailing the system administrator. #########" >> sendEmail.log
sendEmail \
		-f $EMAIL_FROM \
		-t $EMAIL_TO \
		-u $EMAIL_SUBJ \
		-s $SMTP_SRV \
		-xu $SMTP_USER \
		-xp $SMTP_PASS \
		-o message-file=$LOG_FILE \
		-o tls=yes \
		-q -l /var/log/sendEmail.log
}
#------------------------------ END FUNCTION --------------------------------------------#

#----- start doing stuff
# if log exist rename
if [ -f "${LOG_FILE}" ]; then
	mv -f $LOG_FILE $LOG_FILE.old
fi	
# call check_deps
check_deps
if [ $? -gt 0 ]; then
        echo "dependency checking failed. Aborting backup." >> ${LOG_FILE}
		send_email
		exit 0
fi
# cheching if mount point exist
chk_mntpoint
if [ $? -gt 0 ]; then
        echo "mount point creation failed. Aborting backup." >> ${LOG_FILE}
		send_email
		exit 0
fi
# call mount_target
mount_target
if [ $? -gt 0 ]; then
        echo "mount target failed. Aborting backup." >> ${LOG_FILE}
		send_email
		exit 0
fi
#call chk_free_space
chk_free_space
if [ $? -gt 0 ]; then
        echo "check free space failed. Aborting backup." >> ${LOG_FILE}
		send_email
		exit 0
fi
#call check_dst_dir
check_dst_dir
if [ $? -gt 0 ]; then
        echo "check destination dir failed. Aborting backup." >> ${LOG_FILE}
		send_email
		exit 0
fi
#call create_arch
create_arch
if [ $? -gt 0 ]; then
        echo "archive creation failed. Aborting backup." >> ${LOG_FILE}
		send_email
		exit 0
fi

#clean old files
echo "Removing backup archives from the disk that are older than 10 days." >> ${LOG_FILE}
find $MOUNT_POINT/$DST_DIR/ -type f -mtime +10 -exec rm {} \; >> ${LOG_FILE}
#umount
umount_target

# end logging
echo "====================== RUN END on ${DATE} at `date +%H:%M:%S` =============================" >> ${LOG_FILE}

# if everything OK send log via mail
send_email
